                    member()


                    MAP
                    END

    include('TestDllProcFinder.inc'),once
    include('debuger.inc'),once


FileName            CSTRING(FILE:MaxFilePath+FILE:MaxFileName+1),STATIC     ! File name for input and output files

!! LIBfile is used to read and write import library files
!
LIBfile             FILE,DRIVER('DOS','/FILEBUFFERS=20'),PRE(LIB),CREATE,NAME(FileName)
Record                  RECORD
RawBytes                    BYTE,DIM(1024)
header                      GROUP,OVER(RawBytes)
typ                             BYTE             ! OMF record type = 88H (Coment)
len                             USHORT           ! Size of OMF record to follow
kind                            USHORT           ! Comment kind = 0A000H
bla                             BYTE             ! Always 1 for our purposes
ordflag                         BYTE             ! ditto
                            END
! For the records we want, the header is follower by the pubname
! and modname in PSTRING format, then the ordinal export number (USHORT)

pstringval                  PSTRING(128),OVER(RawBytes)
ushortval                   USHORT,OVER(RawBytes)
                        END
                    END

TxtFile             FILE,PRE(Txt),DRIVER('ASCII','/FILEBUFFERS=20'),CREATE,NAME(FileName)
Record                  RECORD
Line                        STRING(256)
                        END
                    END

! EXEfile is used for reading NE and PE format executable files

EXEfile             FILE,DRIVER('DOS','/FILEBUFFERS=20'),PRE(EXE),NAME(FileName)
Record                  RECORD
RawBytes                    BYTE,DIM(1024)
cstringval                  CSTRING(128),OVER(RawBytes)
pstringval                  PSTRING(128),OVER(RawBytes)
ulongval                    ULONG,OVER(RawBytes)
ushortval                   USHORT,OVER(RawBytes)

! DOSheader is the old exe (stub) header format
DOSheader                   GROUP,OVER(RawBytes)
dos_magic                       STRING(2)         ! contains 'MZ'
dos_filler                      USHORT,DIM(29)    ! we don't care about these fields
dos_lfanew                      ULONG             ! File offset of new exe header
                            END

! NEheader is the new exe (16-bit) header format
NEheader                    GROUP,OVER(RawBytes)
ne_magic                        STRING(2)         ! Contains 'NE'
ne_ver                          BYTE
ne_rev                          BYTE
ne_enttab                       USHORT
ne_cbenttab                     USHORT
ne_crc                          LONG
ne_flags                        USHORT
ne_autodata                     USHORT
ne_heap                         USHORT
ne_stack                        USHORT
ne_csip                         ULONG
ne_sssp                         ULONG
ne_cseg                         USHORT
ne_cmod                         USHORT
ne_cbnrestab                    USHORT
ne_segtab                       USHORT
ne_rsrctab                      USHORT
ne_restab                       USHORT
ne_modtab                       USHORT
ne_imptab                       USHORT
ne_nrestab                      ULONG
ne_cmovent                      USHORT
ne_align                        USHORT
ne_rescount                     USHORT
ne_osys                         BYTE
ne_flagsother                   BYTE
ne_gangstart                    USHORT
ne_ganglength                   USHORT
ne_swaparea                     USHORT
ne_expver                       USHORT           ! Expected Window version number
                            END

! PEheader is the flat-model (32-bit) header format (PE signature)
PEheader                    GROUP,OVER(RawBytes)
pe_signature                    ULONG
pe_machine                      USHORT
pe_nsect                        USHORT
pe_stamp                        ULONG
pe_psymbol                      ULONG
pe_nsymbol                      ULONG
pe_optsize                      USHORT
pe_character                    USHORT
                            END

! Optheader is the "optional header" that follows the PEheader

OptHeader                   GROUP,OVER(RawBytes)
opt_Magic                       USHORT
opt_MajorLinkerVer              BYTE
opt_MinorLinkerVer              BYTE
opt_SizeOfCode                  ULONG
opt_SizeOfInitData              ULONG
opt_SizeOfUninit                ULONG
opt_EntryPoint                  ULONG
opt_BaseOfCode                  ULONG
opt_BaseOfData                  ULONG
opt_ImageBase                   ULONG
opt_SectAlignment               ULONG
opt_FileAlignment               ULONG
opt_MajorOSVer                  USHORT
opt_MinorOSVer                  USHORT
opt_MajorImageVer               USHORT
opt_MinorImageVer               USHORT
opt_MajorSubVer                 USHORT
opt_MinorSubVer                 USHORT
opt_Reserved1                   ULONG
opt_SizeOfImage                 ULONG
opt_SizeOfHeaders               ULONG
opt_CheckSum                    ULONG
opt_Subsystem                   USHORT
opt_DllChar                     USHORT
opt_StackReserve                ULONG
opt_StackCommit                 ULONG
opt_HeapReserve                 ULONG
opt_HeapCommit                  ULONG
opt_LoaderFlags                 ULONG
opt_DataDirNum                  ULONG
                            END

! The Optional header is followed by an array of the following structures

DataDir                     GROUP,OVER(RawBytes)
data_VirtualAddr                ULONG
data_Size                       ULONG
                            END

! SectHeader describes a section in a PE file
SectHeader                  GROUP,OVER(RawBytes)
sh_SectName                     CSTRING(8)
sh_VirtSize                     ULONG
sh_PhysAddr                     ULONG,OVER(sh_VirtSize)
sh_VirtAddr                     ULONG
sh_RawSize                      ULONG
sh_RawPtr                       ULONG
sh_Reloc                        ULONG
sh_LineNum                      ULONG
sh_RelCount                     USHORT
sh_LineCount                    USHORT
sh_Character                    ULONG
                            END

! ExpDirectory is at start of a .edata section in a PE file
ExpDirectory                GROUP,OVER(RawBytes)
exp_Character                   ULONG
exp_stamp                       ULONG
exp_Major                       USHORT
exp_Minor                       USHORT
exp_Name                        ULONG
exp_Base                        ULONG
exp_NumFuncs                    ULONG
exp_NumNames                    ULONG
exp_AddrFuncs                   ULONG
exp_AddrNames                   ULONG
exp_AddrOrds                    ULONG
                            END
                        END
                    END

newoffset           ULONG   ! File offset to NE/PE header





dbg                 Debuger


   
  
TestDllProcFinder.construct PROCEDURE
    CODE
    self.ExportQ &= new(ExportQueue)
    
TestDllProcFinder.Destruct  PROCEDURE
    CODE
    free(self.ExportQ)
    dispose(self.ExportQ)

TestDllProcFinder.GetExportedTestProcedures procedure(string DllName,TestProcedureQueue procq)
x                                               long
errcode                                         long
err                                             STRING(100)
retval                                          byte


    CODE
    dbg.init('FindTestProcedures')
    dbg.debugactive = true
    dbg.Message('filename: ' & dllname)
    filename = dllname
    free(ProcQ)
    free(self.ExportQ)
    errcode = self.ReadExecutable()
    retval = false
    if ~retval
        free(procq)
        loop x = 1 to records(self.ExportQ)
            get(self.ExportQ,x) 
            if sub(upper(self.ExportQ.Symbol),1,5) = 'TEST_'
                !procq.ProcName = sub(self.exportq.Symbol,1,instring('@',self.exportq.Symbol,1,1)-1)
                procq.TestName = self.exportq.Symbol
                procq.Passed = False
                procq.TestResult = ''
                !procq.ordinal = self.exportq.ordinal
                add(procq)
                retval = true
            END
        END
    ELSE
        err = error()
    END
    if ~records(ProcQ)
        if errcode
            procq.TestName = 'Error opening DLL: ' & err
            add(procq)
        ELSE
            procq.TestName = 'No test procedures found in DLL'
        END
    END
    return retval
        
 
            
            
    

! ReadExecutable gets export table from 16 or 32-bit file or LIB file

TestDllProcFinder.ReadExecutable    PROCEDURE ()

sectheaders                             ULONG   ! File offset to section headers
sections                                USHORT  ! File offset to section headers
VAexport                                ULONG   ! Virtual address of export table, according to data directory
                    ! This is used as an alternative way to find table if .edata not found
    CODE
    dbg.Message('Attempting to open  DLL ' & filename)
    OPEN(EXEfile, 0)
    e# = errorcode()
    if e#
        dbg.Message('Errorcode ' & e# & ' ' & error())
        return errorcode()
!    if errorcode()
!        return errorcode()
    END
    GET(EXEfile, 1, SIZE(EXE:DOSheader))
    IF EXE:dos_magic = 'MZ' THEN
        newoffset = EXE:dos_lfanew
        GET(EXEfile, newoffset+1, SIZE(EXE:PEheader))

        IF EXE:pe_signature = 04550H THEN
            sectheaders = EXE:pe_optsize+newoffset+SIZE(EXE:PEheader)
            sections = EXE:pe_nsect
       ! Read the "Optional header"
            GET(EXEfile, newoffset+SIZE(EXE:PEheader)+1, SIZE(EXE:Optheader))
            IF EXE:opt_DataDirNum THEN
          ! First data directory describes where to find export table
                GET (EXEfile, newoffset+SIZE(EXE:PEheader)+SIZE(EXE:OptHeader)+1, SIZE(EXE:DataDir))
                VAexport = EXE:data_VirtualAddr
            END

            LOOP i# = 1 TO sections
                GET(EXEfile,sectheaders+1,SIZE(EXE:sectheader))
                sectheaders += SIZE(EXE:sectheader)
                IF EXE:sh_SectName = '.edata' THEN
                    self.DumpPEExportTable(EXE:sh_VirtAddr, EXE:sh_VirtAddr - EXE:sh_rawptr)
                ELSIF EXE:sh_VirtAddr <= VAexport AND |
                    EXE:sh_VirtAddr+EXE:sh_RawSize > VAexport
                    self.DumpPEExportTable(VAexport, EXE:sh_VirtAddr - EXE:sh_rawptr)
                END
            END
        ELSE
            GET(EXEfile, newoffset+1, SIZE(EXE:NEheader))
            self.DumpNEExports
        END
    END
    CLOSE(EXEfile)
    return 0

 !DumpPEexportTable gets export table from a PE format file (32-bit)

TestDllProcFinder.DumpPEExportTable PROCEDURE (ULONG VirtualAddress, ULONG ImageBase)

NumNames                                ULONG,AUTO
Names                                   ULONG,AUTO
Ordinals                                ULONG,AUTO
Base                                    ULONG,AUTO

j                                       UNSIGNED,AUTO

    CODE
    GET(EXEfile, VirtualAddress-ImageBase+1, SIZE(EXE:ExpDirectory))
    NumNames = EXE:exp_NumNames
    Names    = EXE:exp_AddrNames
    Ordinals = EXE:exp_AddrOrds
    Base     = EXE:exp_Base
    GET(EXEfile, EXE:exp_Name-ImageBase+1, SIZE(EXE:cstringval))

    self.ExportQ.Module   = EXE:cstringval
    self.ExportQ.Symbol    = EXE:cstringval
    self.ExportQ.TreeLevel = 1
    self.ExportQ.Icon      = 1
    self.ExportQ.Ordinal   = 0
    self.ExportQ.Libno     = LastLib
    ADD(self.ExportQ)

    self.ExportQ.TreeLevel = 2
    self.ExportQ.Icon      = 0

    LOOP j = 0 TO NumNames - 1
        GET(EXEfile, Names+j*4-ImageBase+1, SIZE(EXE:ulongval))
        GET(EXEfile, EXE:ulongval-ImageBase+1, SIZE(EXE:cstringval))
        self.ExportQ.Symbol = EXE:cstringval

        GET(EXEfile, Ordinals+j*2-ImageBase+1, SIZE(EXE:ushortval))
        self.ExportQ.Ordinal = EXE:ushortval+Base
        self.ExportQ.Libno   = LastLib + 1
        ADD(self.ExportQ)
    END
!!
!!! DumpNEexports gets export table from a NE format file (16-bit)
!!
TestDllProcFinder.DumpNEExports     PROCEDURE ()

j                                       LONG,AUTO
r                                       LONG,AUTO

    CODE
!    ! First get the module name - stored as first entry in resident name table
!    j = EXE:ne_nrestab+1
!    r = newoffset+EXE:ne_restab+1
!    GET(EXEfile, r, SIZE(EXE:pstringval))
!
!    self.ExportQ.Module    = EXE:pstringval
!    self.ExportQ.Symbol    = EXE:pstringval
!    self.ExportQ.Ordinal   = 0
!    self.ExportQ.TreeLevel = 1
!    self.ExportQ.Icon      = 1
!    self.ExportQ.Libno     = LastLib
!    ADD(self.ExportQ)
!
!    r += LEN(EXE:pstringval)+1    !move past module name
!    r += 2                        !move past ord#
!
!! Now pull apart the resident name table. First entry is the module name, read above
!
!    self.ExportQ.TreeLevel = 2
!    self.ExportQ.Icon = 0
!
!    LOOP
!        GET(EXEfile, r, SIZE(EXE:pstringval))
!        IF LEN(EXE:pstringval) = 0 THEN
!            BREAK
!        END
!        self.ExportQ.Symbol = EXE:pstringval
!        r += LEN(EXE:pstringval)+1
!        GET(EXEfile, r, SIZE(EXE:ushortval))
!        r += 2
!        self.ExportQ.Ordinal = EXE:ushortval
!        self.ExportQ.Libno   = LastLib + 1
!        ADD(self.ExportQ)
!    END
!
!! Now pull apart the non-resident name table. First entry is the description, and is skipped
!    GET(EXEfile, j, SIZE(EXE:pstringval))
!    j += LEN(EXE:pstringval)+1
!    GET(EXEfile, j, SIZE(EXE:ushortval))
!    j += 2
!    LOOP
!        GET(EXEfile, j, SIZE(EXE:pstringval))
!        IF LEN(EXE:pstringval)=0 THEN
!            BREAK
!        END
!        self.ExportQ.Symbol = EXE:pstringval
!        j += LEN(EXE:pstringval)+1
!        GET(EXEfile, j, SIZE(EXE:ushortval))
!        j += 2
!        self.ExportQ.Ordinal = EXE:ushortval
!        self.ExportQ.libno = LastLib + 1
!        ADD(self.ExportQ)
!    END
!!
!!! WriteLib writes out all info in the export Q to a LIB file
!!
!!TestDllProcFinder.WriteLib            PROCEDURE ()
!!
!!rec                     UNSIGNED,AUTO
!!
!!    CODE
!!    CREATE(LIBfile)
!!    OPEN(LIBfile)
!!
!!    LOOP rec = 1 TO RECORDS(ExportQ)
!!        GET(ExportQ, rec)
!!        IF ExportQ.TreeLevel = 2 THEN
!!        ! Record size is length of the strings, plus two length bytes, a two byte
!!        ! ordinal, plus the header length (excluding the first three bytes)
!!            LIB:typ = 88H
!!            LIB:kind = 0A000H
!!            LIB:bla = 1
!!            LIB:ordflag = 1
!!            LIB:len = LEN(CLIP(exq:module))+LEN(CLIP(exq:Symbol))+2+2+SIZE(LIB:header)-3
!!            ADD(LIBfile, SIZE(LIB:header))
!!            LIB:pstringval = ExportQ.Symbol
!!            ADD(LIBfile, LEN(LIB:pstringval)+1)
!!            LIB:pstringval = ExportQ.Module
!!            ADD(LIBfile, LEN(LIB:pstringval)+1)
!!            LIB:ushortval = ExportQ.Ordinal
!!            ADD(LIBfile, SIZE(LIB:ushortval))
!!        END
!!    END
!!    CLOSE(LIBfile)
!
!!! Readlib reads back in a LIB file output by WriteLib above or by IMPLIB etc
!!
!!TestDllProcFinder.ReadLib             PROCEDURE
!!
!!ii                      LONG,AUTO
!!jj                      LONG,AUTO
!!ordinal                 USHORT,AUTO
!!lastmodule              CSTRING(21)
!!modulename              CSTRING(21)
!!Symbolname              CSTRING(129),AUTO
!!
!!    CODE
!!    OPEN(LIBfile, 40h)
!!    ii = 1
!!
!!    LOOP 
!!        GET(LIBfile, ii, SIZE(LIB:header))     ! Read next OMF record
!!        IF ERRORCODE()
!!            BREAK                              ! All done
!!        END
!!        IF LIB:typ = 0 OR LIB:len = 0
!!            BREAK
!!        END
!!
!!        jj   = ii + SIZE(LIB:header)             ! Read export info from here
!!        ii  += LIB:len + 3                       ! Read next OMF record from here
!!
!!        IF LIB:typ = 88H AND LIB:kind = 0A000H AND LIB:bla = 1 AND LIB:ordflag = 1 THEN
!!            GET(LIBfile, jj, SIZE(LIB:pstringval))
!!            Symbolname = LIB:pstringval
!!            jj += LEN(LIB:Pstringval)+1
!!            GET(LIBfile, jj, SIZE(LIB:pstringval))
!!            modulename = LIB:pstringval
!!            jj += LEN(LIB:Pstringval)+1
!!            GET(LIBfile, jj, SIZE(LIB:ushortval))
!!            ordinal = LIB:ushortval
!!
!!            IF modulename <> lastmodule      ! A LIB can describe multiple DLLs
!!                IF  lastmodule[1] <> '<0>'
!!                    LastLib += 2
!!                END
!!                lastmodule = modulename
!!
!!                ExportQ.TreeLevel = 1
!!                ExportQ.Icon      = 1
!!                ExportQ.Symbol    = modulename
!!                ExportQ.Module    = modulename
!!                ExportQ.Ordinal   = 0
!!                ExportQ.Libno     = LastLib
!!                ADD(ExportQ)
!!            END
!!
!!            ExportQ.TreeLevel = 2
!!            ExportQ.Icon      = 0
!!            ExportQ.Symbol    = SymbolName
!!            ExportQ.Module    = modulename
!!            ExportQ.Ordinal   = ordinal
!!            ExportQ.Libno     = LastLib + 1
!!            ADD(ExportQ)
!!        END
!!    END
!!    CLOSE(LIBfile)
!!
!
!!WriteText           PROCEDURE
!!
!!Rec                     UNSIGNED,AUTO
!!Opened                  BYTE,AUTO
!!Dot                     UNSIGNED,AUTO
!!
!!    CODE
!!    SORT (ExportQ, +ExportQ.libno, +ExportQ.Ordinal)
!!    Opened = FALSE
!!    LOOP Rec = 1 TO RECORDS (ExportQ)
!!        Get(ExportQ, rec)
!!        IF ExportQ.Ordinal <> 0
!!            IF Opened
!!                TxtFile.Line = '  ' & ExportQ.Symbol & ' @' & ExportQ.Ordinal
!!                ADD(TxtFile)
!!                CYCLE
!!            END
!!            FileName = 'DEFAULT.EXP'
!!        ELSE
!!            IF STATUS (TxtFile)
!!                CLOSE(TxtFile)
!!            END
!!            dot = INSTRING ('.', ExportQ.Symbol, 1, 1)
!!            IF  dot
!!                FileName = ExportQ.Symbol [1 : dot] & 'EXP'
!!            ELSE
!!                FileName = ExportQ.Symbol & '.EXP'
!!            END
!!            opened = TRUE
!!        END
!!
!!        MESSAGE('Export file, ' & CLIP(FileName) & ' being written in ' & |
!!            LongPath(PATH()),'Library exported!',ICON:Exclamation)
!!
!!        CREATE (TxtFile)
!!        OPEN (TxtFile)
!!        TxtFile.Line = 'EXPORTS'
!!        ADD (TxtFile)
!!        IF  NOT opened
!!            TxtFile.Line = '  ' & ExportQ.Symbol & ' @' & ExportQ.Ordinal
!!            ADD (TxtFile)
!!            opened = TRUE
!!        END
!!    END
!!    CLOSE (TxtFile)

